﻿
#include <stdio.h>

#include <fpdf_doc.h>
#include <fpdf_text.h>

#include "libs.h"

#define MAC_CHAPTER_LEN (10 * 1024 * 1024)
//one chapter max 
static char chapter_content[MAC_CHAPTER_LEN];
static int chapter_content_len = 0;
char magic_start_char = 1;
extern FPDF_DOCUMENT gdoc;



//only handle one page
//#define PAGE_BY_PAGE 1

void pdflib_init(void)
{
	FPDF_LIBRARY_CONFIG config;
	config.version = 2;
	config.m_pUserFontPaths = NULL;
	config.m_pIsolate = NULL;
	config.m_v8EmbedderSlot = 0;

	FPDF_InitLibraryWithConfig(&config);
}

FPDF_DOCUMENT pdflib_open_doc(const char* path)
{
	int len;
	char* utf_path = to_utf((char*)path, &len);
	FPDF_DOCUMENT doc = FPDF_LoadDocument(utf_path, NULL);
	free(utf_path);
	if (doc)
		return doc;

	unsigned long err = FPDF_GetLastError();
	printf("Load pdf docs unsuccessful: ");
	switch (err) {
	case FPDF_ERR_SUCCESS:
		printf("Success");
		break;
	case FPDF_ERR_UNKNOWN:
		printf("Unknown error");
		break;
	case FPDF_ERR_FILE:
		printf("File not found or could not be opened");
		break;
	case FPDF_ERR_FORMAT:
		printf("File not in PDF format or corrupted");
		break;
	case FPDF_ERR_PASSWORD:
		printf("Password required or incorrect password");
		break;
	case FPDF_ERR_SECURITY:
		printf("Unsupported security scheme");
		break;
	case FPDF_ERR_PAGE:
		printf("Page not found or content error");
		break;
	default:
		printf("Unknown error %ld", err);
	}
	printf(".\n");
	return NULL;
}

//page_number: 1 based
//return 0 if OK
char* pdflib_page_text(FPDF_DOCUMENT doc, int page_number, int* out_len)
{
	char* buf = NULL;
	FPDF_PAGE page = NULL;
	FPDF_TEXTPAGE text_page = NULL;

	page = FPDF_LoadPage(doc, page_number - 1);
	if (!page) {
		printf("can't load %d page\n", page_number);
		return NULL;
	}

	text_page = FPDFText_LoadPage(page);
	if (!text_page) {
		printf("can't load text content of %d page\n", page_number);
		goto EXIT;
	}

	int text_len = FPDFText_CountChars(text_page);
	if (text_len <= 0)
		goto EXIT;

	unsigned short* buffer = malloc((text_len + 1) * sizeof(unsigned short));
	memset(buffer, 0, (text_len + 1) * sizeof(unsigned short));
	int actual_len = FPDFText_GetText(text_page, 0, text_len, buffer);

	buf = u16_to_u8((char*)buffer, &actual_len);
	free(buffer);

	if (out_len)
		*out_len = actual_len;

EXIT:
	if(text_page)
		FPDFText_ClosePage(text_page);
	if (page)
		FPDF_ClosePage(page);
	return buf;
}

void pdflib_toc(FPDF_DOCUMENT doc, FPDF_BOOKMARK toc, int level, struct toc_link* head)
{
	FPDF_BOOKMARK root;
	unsigned long title_len;
	int toc_num = 0;
	char titles[MAX_CHAPTER_NAME_LEN]; //max len is 1024 bytes
	char* title_out;

	//max 2 levels: level 0 and level 1
	if (level > 1)
		return;

	if (level == 0) {
		root = FPDFBookmark_GetFirstChild(doc, NULL);
	} else {
		root = toc;
	}

	FPDF_BOOKMARK child = NULL;
	FPDF_DEST dest = NULL;
	int page_index = 0;

	while (root) {
		//page index
		dest = FPDFBookmark_GetDest(doc, root);
		if (dest) {
			page_index = FPDFDest_GetDestPageIndex(doc, dest);
		}

		//title
		title_len = FPDFBookmark_GetTitle(root, NULL, 0);
		if (!title_len)
			continue;
		FPDFBookmark_GetTitle(root, titles, title_len);
		title_out = u16_to_u8(titles, &title_len);
		if (level)
			printf("----");
		printf_utf8(title_out);
		printf(" --%d\n\n", page_index + 1);
		//build toc list
		insert_list(head, title_out, page_index + 1, level);
		free(title_out);
		toc_num++;

		child = FPDFBookmark_GetFirstChild(doc, root);
		if (child) {
			pdflib_toc(doc, child, level + 1, head);
		}

		root = FPDFBookmark_GetNextSibling(doc, root);
	}
}

void init_list(struct toc_link* head, int total_pages)
{
	memset(head, 0, sizeof(struct toc_link));
	head->chapter = -1;
	head->num_pages = total_pages;
	INIT_LIST_HEAD(&head->list);
}

/*
* attach to tail
* name: title name
* start: start page index
* level: 0 is top level; 1 is second level
*/
void insert_list(struct toc_link* head, char* name, int start, int level)
{
	struct toc_link* node;
	struct toc_link* pre;

	node = malloc(sizeof(struct toc_link));
	if (!node)
		return;

	strcpy(node->chapter_name, name);
	node->level = level;
	node->start_page = start;
	node->end_page = head->num_pages; //total
	//only valid the last chapter, may be modified by next node 
	node->num_pages = head->num_pages - start + 1;

	list_add_tail(&node->list, &head->list);
	pre = (struct toc_link*)list_prev_entry(node, struct toc_link, list);

	node->chapter = pre->chapter + 1;
	if (node->chapter) {
		pre->end_page = node->start_page - 1;
		if (pre->end_page < pre->start_page)
			pre->end_page = pre->start_page;
		pre->num_pages = pre->end_page - pre->start_page + 1;
	}
}

void destroy_list(struct toc_link* head)
{
	struct toc_link* cur;
	struct toc_link* n;

	//struct list_head* pos;
	//list_for_each(pos, &head->list) {

	//list_for_each_entry(cur, &head->list, list, struct toc_link) {
	list_for_each_entry_safe(cur, n, &head->list, list, struct toc_link) {
		list_del(&cur->list);
		free(cur);
	}
}

char* goto_next_words(char* cur)
{
	char* head = cur;
	//skip CRLF
	while (*head) {
		if (*head == 0xd || *head == 0xa || *head == 0x9)
			head++;
		else
			break;
	}

	return head;
}

char* goto_words_tail(char* cur)
{
	char* head = cur;
	//skip CRLF
	while (*head) {
		if (*head == 0xd || *head == 0xa || *head == 0x9)
			break;
		else
			head++;
	}

	return head;
}

/*
* return 1 if reach tail
* return 0 otherwise
*/
int sscanf_words(char* src, char* dst, size_t* words_len, char** new_head)
{
	char* head = src;
	head = goto_next_words(head);
	if (*head == 0)
		return 1;

	char* tail;
	tail = goto_words_tail(head);

	size_t len = 0;
	char* cur = dst;
	while (head != tail) {
		//remove space
		/*
		if (*head == ' ') {
			head++;
			continue;
		}*/
		*cur++ = *head++;
		len++;
	}

	dst[len] = '\n';
	len++;
	dst[len] = 0;
	if(words_len)
		*words_len = len;
	*new_head = tail;

	return 0;
}

/*
* index > 4, need check CRLF。如果有回车换行符，才能认定为本句结束。
否则，需要读入下一行


以冒号作为行结尾
让我们来总结一下，当一名合格的瑜伽修行者，一名合格的圣者弟子，需要具备哪些要素：
1.来到人生质变（飞跃）的点，从内心深处渴望蜕变，真正意识到需要一个导师。
2.尊重知识、尊重瑜伽和尊师重道。——这一点是基本的、是必须的。


以双引号作为行结尾，独立的一行，示例：
“陈哥，刚睡醒？还好今天老师心情好，没点名，要不然你可就惨了！”
这就叫“心起为境”。

以双引号作为行结尾，前面面必须是 ！ 。 ？，否则不是结尾

*/
char keywords_stop[][4] =
{
	"；", //0
	"。", //1
	"！", //2
	"？", //3
	"】", //4

	"：", //5
	"”", //6
	""
};


int is_end_words(char* str)
{
	int count = sizeof(keywords_stop) / 4 - 1;
	int i, end;

	end = 0;
	char* head;
	for (i = 0; i < count; i++) {
		if (!memcmp(str, keywords_stop[i], 3)) {
			if (i <= 4) {
				end = 1;
				break;
			}
			//i > 4
			head = str + 3;
			if (*head == '\r' || *head == '\n') {
				end = 1;
				break;
			}
		}
	}

	//"
	if (i == 6) {
		end = 0;
		head = str - 3;
		for (i = 1; i < 4; i++) {
			if (!memcmp(head, keywords_stop[i], 3)) {
				end = 1;
				break;
			}
		}		
	}

	return end;
}


//word is a digital?
int is_digital(char* word, size_t len)
{
	int i;
	for(i = 0; i < len; i++){
		if (word[i] >= '0' && word[i] <= '9')
			continue;
		return 0;
	}
	return 1;
}

//return 1 if found fullstop
int goto_fullstop(char* src, char* dst, char** new_head)
{
	char* head = src;
	head = goto_next_words(head);
	if (*head == 0) //goto tail
		return 1;

	int len = 0;
	while (*head) {
		if (is_end_words(head)) {
			*new_head = head + 3; //include fullstop
			dst[len + 3] = 0;
			return 1;
		}
		if (*head == '\r' || *head == '\n') {
			//no fullstop symbol in this line
			return 1;
		}
		head++;
		len++;
	}
	return 0;
}

static char gwords[1024 * 16];

//only handle one page
void reformat_text(FILE* fp, char* buf)
{
	char* head = buf;
	size_t word_len;
	int end = 0;
	int line_number = -1;
	char* ori_head;

	int fullstop = 0;
	int nextline = 0;
	while (!end) {
		end = sscanf_words(head, gwords, &word_len, &head);
		if (end)
			break;
		line_number++;
		if (line_number == 0) {//first line
			if(!is_digital(gwords, word_len))
				fprintf(fp, "%s\n", gwords);
			continue;
		}

		if (word_len < 36 && nextline == 0) { //standalone line? guess
			fprintf(fp, "%s\n", gwords);
			continue;
		}

		//find full words
		if (is_end_words(gwords + word_len - 3)) {
			fprintf(fp, "%s\n", gwords);
			nextline = 0;
			continue;
		}
		nextline = 1;
		//read next line
		ori_head = head;
		end = sscanf_words(head, gwords + word_len, NULL, &head);
		//try to find fullstop within next line
		goto_fullstop(ori_head, gwords + word_len, &head);
		fprintf(fp, "%s\n", gwords);
	}
}

//I, V, X
char* skip_greece_number(char* buf)
{
	char* head = buf;
	int loop = 1;
	while (loop) {
		switch (*head) {
		case 'I':
		case 'i':
		case 'V':
		case 'v':
		case 'x':
		case 'X':
			head++;
			break;
		default:
			loop = 0;
		}
	}
	return head;
}

/*
* if title at top line, return page_text;
* otherwise, return title position
*/
char* is_title_at_top(char* page_text, char* title)
{
	char* head = page_text;

	size_t word_len;
	char* temp;
	sscanf_words(head, gwords, &word_len, &temp);
	if (is_digital(gwords, word_len - 1)) {
		//skip page number
		head = temp;
	}

	//skip Greece number page
	head = skip_greece_number(head);

	//this is should title line
	//ignore space
	int end = 0;
	while (!end) {
		end = sscanf_words(head, gwords, &word_len, &temp);
		if (word_len == 2 && gwords[0] == ' ')
			head = temp;
		else
			break;
	}

	//this is should title line
	size_t title_len = strlen(title) - 1;
	if (!memcmp(gwords, title, title_len))
		return page_text;

	//Chinese space VS English space
	size_t title_cn_len = strlen(gwords) - 1;
	if((title_cn_len - title_len) == 2)
		return page_text;

	head = strstr(page_text, title);
	if (!head)
		return NULL;

	int count = 0;
	char* tail;
	//check if tiltle, title at standalone line
	while (count < 3) {
		temp = head - 1;
		tail = head + title_len + 1;
		if ((*temp == '\r' || *temp == '\n') && 
			(*tail == '\r' || *tail == '\n' || *tail == ' ' || *tail == 0)) {
			return head;
		}
		count++;
		head = strstr(tail, title);
		if (!head)
			return NULL;
	}
	return page_text;
}

//write to chapter_content[]
void write_to_chapter_buf(char* all_text, char* page_text, int page_text_len,
	int cur_page, struct toc_link* cur, struct toc_link* next)
{
	char* start = page_text;
	char* end = page_text;
	if (cur_page == cur->start_page) {
		start = is_title_at_top(page_text, cur->chapter_name);
		if (!start)
			return;
	}

	if (cur_page == cur->end_page && cur->start_page == cur->end_page) {
		//3 chapters at one page
		end = is_title_at_top(page_text, next->chapter_name);
		if (end != page_text && cur->chapter && end)
			*end = 0;
	}

	if (cur_page == (cur->end_page + 1)) {
		end = is_title_at_top(page_text, next->chapter_name);
		if (end == page_text)
			return;

		if (!end && cur->level) {
			//wrong page number, read more page
			char* text;
			int text_len;
			text = pdflib_page_text(gdoc, cur_page + 1, &text_len);
			if (!text)
				return;

			char* new_end;
			new_end = is_title_at_top(text, next->chapter_name);
			if (!new_end)
				return;
			//found the next chapter, so keep this page, cur_page == (cur->end_page + 1)
			goto save_page;
		}
		if(end)
			*end = 0;
	}

save_page:
	sprintf(all_text + chapter_content_len, "%c%s\n", magic_start_char, start);
	page_text_len = (int)strlen(start);
	page_text_len++;
	chapter_content_len = chapter_content_len + page_text_len + 1;
	if (chapter_content_len >= MAC_CHAPTER_LEN) {
		printf("too large chapter, chapter len is %d, max buf is %d\n",
			chapter_content_len, MAC_CHAPTER_LEN);
		return;
	}
}

//return 1: first line
//return 2: end
//return 0: otherwise
int get_next_line(char** buf, char* dest)
{
	int ret = 0;
	char* head = *buf;
	if (*head == magic_start_char) {
		ret = 1;
		head++;
	}

	head = goto_next_words(head);

	int len = 0;
	while (1) {
		dest[len] = *head;
		len++;
		if (*head == 0) {
			ret = 2;
			break;
		}
		if (*head == '\r' || *head == '\n') {
			dest[len] = 0;
			*buf = goto_next_words(head);
			break;
		}
		head++;
	}
	return ret;
}

char small_title[][7] =
{
	"一、",
	"二、",
	"三、",
	"四、",
	"五、",
	"六、",
	"七、",
	"八、",
	"九、",
	""
};

int is_small_title(char* str)
{
	int i;
	int ret = 0;
	for (i = 0; i < 9; i++) {
		if (!memcmp(str, small_title[i], 6)) {
			ret = 1;
			break;
		}
	}
	return ret;
}

/*
* handle cross page issue

while(1)
	get next line;
	if reach to chapter end
		write left to file;
		break;

	if first line of page
		...
		continue;

	org_to_words; //saved to gwords[]
	good = is_end_words();
	if(good)
		write to file;
		continue;

	//not full words
*/
static char one_line_buf[2 * 1024];
void reformat_chapter(FILE* fp, char* chapter_content, int chapter_len)
{
	int line_flag = 0;
	char* chapter_head = chapter_content;
	size_t word_len, total_len;
	char* new_head;

	
	int nextline = 0; //need reorg line
	int is_toc_chapter = 0;
	total_len = 0;

	while (1) {
		line_flag = get_next_line(&chapter_head, one_line_buf);
		if (line_flag == 2) {
			//end of this chapter
			if (nextline) {
				sscanf_words(one_line_buf, gwords + total_len, &word_len, &new_head);
				fprintf(fp, "%s\n", gwords);
			}
			break;
		}

		//org_to_words; saved to gwords[]
		sscanf_words(one_line_buf, gwords + total_len, &word_len, &new_head);

		//check if TOC chapter
		if (word_len == 4 && !memcmp(gwords, "目", 3)) {
			is_toc_chapter = 1;
		}

		if (line_flag == 1) { //first line of one page
			if (nextline) {
				if (is_digital(gwords + total_len, word_len - 1)) {
					gwords[total_len] = 0; //ignore page number
					continue;
				}
			} else {
				if (is_digital(gwords, word_len - 1))
					continue;
			}
		}

		//一、xxx
		if (is_small_title(gwords)) {
			fprintf(fp, "%s\n", gwords);
			continue;
		}

		if (is_toc_chapter) {
			fprintf(fp, "%s\n", gwords);
			continue;
		}

		if (word_len < 36 && nextline == 0) { //standalone line? guess
			if (word_len == 1)
				continue;
			fprintf(fp, "%s\n", gwords);
			nextline = 0;
			continue;
		}

		if (strstr(gwords, "··················")) {
			//content?
			fprintf(fp, "%s\n", gwords);
			nextline = 0;
			continue;
		}

		//find full words
		if (is_end_words(gwords + total_len + word_len - 4)) {
			fprintf(fp, "%s\n", gwords);
			nextline = 0;
			total_len = 0;
			continue;
		}
		nextline++;
		//remove the CRLF
		word_len--;
		total_len = total_len + word_len;
		gwords[total_len] = 0;
	}
}

//pdf to markdown
int output_to_md(FPDF_DOCUMENT doc, FILE* fp, struct toc_link* head)
{
	struct toc_link* cur;
	int i, text_len;
	char* text;

	struct toc_link* pre;
	struct toc_link* next;
	int page_count = FPDF_GetPageCount(doc);

	list_for_each_entry(cur, &head->list, list, struct toc_link) {
		if (cur->level == 0)
			fprintf(fp, "# %s\n", cur->chapter_name);
		else
			fprintf(fp, "## %s\n", cur->chapter_name);

		//2 chapter in one page
		pre = (struct toc_link*)list_prev_entry(cur, struct toc_link, list);
		next = (struct toc_link*)list_next_entry(cur, struct toc_link, list);

		chapter_content_len = 0;
		for (i = cur->start_page; i <= (cur->end_page + 1); i++) {
			if (page_count == i) {
				//last page of this book
				reformat_chapter(fp, chapter_content, chapter_content_len);
				break;
			}
			printf("ongoing page %d\n", i);
			text = pdflib_page_text(doc, i, &text_len);
			if (!text)
				continue;
#ifdef PAGE_BY_PAGE
			reformat_text(fp, text);
#else
			write_to_chapter_buf(chapter_content, text, text_len, i, cur, next);
#endif
			free(text);
		}

#ifndef PAGE_BY_PAGE
		reformat_chapter(fp, chapter_content, chapter_content_len);
#endif
		
	}
	printf("\nFinished all chapters\nAll was done\n");
	return 0;
}